package tiny.soft.com.common.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import tiny.soft.com.common.bean.ResultBean;
import tiny.soft.com.common.util.NetUtils;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 业务异常统一处理类。如果不包含在此类的异常（Exception.class），将会上升到框架处理。
 * 此类记录日志没有堆栈信息
 * @ClassName BusinessExceptionInterceptor
 * @Author 柳时光
 * @Date 2019/4/19 12:55
 * @Version 1.0
 **/
@ControllerAdvice
@Order(1)
@Slf4j
public class BusinessExceptionInterceptor{

    private static String host;
    private static String port;

    /**
      * 处理统一抛出异常
      * @Author 柳时光
      * @Date 2019/4/30 16:23
      * @param request
      * @param businessException
      * @return com.yinhai.ta404.core.restservice.resultbaen.ResultBean
      **/
    @ExceptionHandler({BusinessException.class})
    @ResponseBody
    public ResultBean appExceptionHandler(HttpServletRequest request, BusinessException businessException){
        this.recordSystemException(request, businessException);
        String errorMsgs = businessException.getMessage();
        if (null == errorMsgs) {
            //没有错误信息，使用内置错误提示。
            log.error("BusinessException msg = {},return default message", errorMsgs);
            return this.interServerError();
        } else {
            //有错误信息的处理
            ResultBean resultBean=new ResultBean();
            resultBean.addErrors(businessException.getErrorCode(), errorMsgs);
            //根据reThrow 和 printStackTrace属性判断是否输出堆栈
            if(businessException.isReThrow()){
                //重新抛出的异常，输出堆栈
                log.error("BusinessException code = {}, msg = {}, exception = ",
                        businessException.getErrorCode(), businessException.getMessage(), businessException.getCause());
            }else{
                if(businessException.isPrintStackTrace()){
                    //手动设置的，输出堆栈
                    log.error("BusinessException code = {}, msg = {}, exception = ",
                            businessException.getErrorCode(), businessException.getMessage(), businessException);
                }else{
                    //默认情况，不输出堆栈
                    log.error("BusinessException code = {}, msg = {}",
                            businessException.getErrorCode(), businessException.getMessage());
                }

            }

            return resultBean;
        }
    }

    /**
      * 处理参数绑定异常
      * @Author 柳时光
      * @Date 2019/4/30 16:23
      * @param request
      * @param bindException
      * @return com.yinhai.ta404.core.restservice.resultbaen.ResultBean
      **/
    @ExceptionHandler({BindException.class})
    @ResponseBody
    public ResultBean bindExceptionHandler(HttpServletRequest request, BindException bindException){
        this.recordSystemException(request, bindException);
        BindingResult bindingResult = bindException.getBindingResult();

        List<ObjectError> allErrors = bindingResult.getAllErrors();
        String[] errorMsgs = new String[allErrors.size()];
        ObjectError oe;
        ResultBean resultBean=new ResultBean();
        for(int i=0; i<allErrors.size(); i++){

            oe = allErrors.get(i);
            if(oe instanceof FieldError){
                FieldError fe = (FieldError)oe;
                resultBean.addErrors("417",fe.getField(),  fe.getDefaultMessage());
            }else{
                resultBean.addErrors("417",oe.getCodes().toString(), oe.getDefaultMessage());
            }
            log.error("BindException arg = {}, msg = {}", new Object[]{oe.getCodes(), oe.getDefaultMessage()});
        }

        return resultBean;
    }

    /**
     * 增加方法参数无效异常拦截处理，因为此非重要异常确会造成大量日志输出，影响性能，所以处理输出简要信息。请注意系统还是得收集异常堆栈，只是不输出日志
     * @Author 柳时光
     * @Date 2019/10/30 15:52
     * @param request
     * @param validException
     * @return com.yinhai.ta404.core.restservice.resultbaen.ResultBean
     **/
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseBody
    public ResultBean methodArgumentNotValidExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException validException) {
        this.recordSystemException(request, validException);
        BindingResult bindingResult = validException.getBindingResult();
        if (null != bindingResult && !bindingResult.getAllErrors().isEmpty()) {
            Iterator var4 = bindingResult.getAllErrors().iterator();
            ResultBean resultBean=new ResultBean();
            while(var4.hasNext()) {
                ObjectError allError = (ObjectError)var4.next();
                FieldError error = (FieldError)allError;
                String field = error.getField();
                String defaultMessage = error.getDefaultMessage();
                resultBean.addErrors("417", field, defaultMessage);
            }

            log.error("MethodArgumentNotValidException msg = {}", validException.getMessage());
            return resultBean;
        } else {
            log.error("MethodArgumentNotValidException bindingResult = {},return default message", bindingResult);
            return this.interServerError();
        }
    }

    /**
     * 处理缺失请求参数异常（MissingServletRequestParameterException），常见于api参数添加了：@RequestParam("x")，请求没有传递x
     * @Author 柳时光
     * @Date 2019/10/30 15:50
     * @param request
     * @param missingServletRequestParameterException
     * @return com.yinhai.ta404.core.restservice.resultbaen.ResultBean
     **/
    @ExceptionHandler({MissingServletRequestParameterException.class})
    @ResponseBody
    public ResultBean methodArgumentNotValidExceptionHandler(HttpServletRequest request,
                                                             MissingServletRequestParameterException missingServletRequestParameterException) {
        this.recordSystemException(request, missingServletRequestParameterException);
        ResultBean resultBean=new ResultBean();
        log.error("MissingServletRequestParameterException (缺失请求参数) param = {}, type = {}", missingServletRequestParameterException.getParameterName(),
                missingServletRequestParameterException.getParameterType());
        resultBean.setError("缺失请求参数："+missingServletRequestParameterException.getParameterName()+", 类型："+
                missingServletRequestParameterException.getParameterType());
        return resultBean;
    }
    /**
     * 请求提交信息无法读取异常（HttpMessageNotReadableException），常见于请求参数格式不对
     * @Author 柳时光
     * @Date 2019/11/14 15:50
     * @param request
     * @param httpMessageNotReadableException
     * @return com.yinhai.ta404.core.restservice.resultbaen.ResultBean
     **/
    @ExceptionHandler({HttpMessageNotReadableException.class})
    @ResponseBody
    public ResultBean httpMessageNotReadableExceptionHandler(HttpServletRequest request,
                                                             HttpMessageNotReadableException httpMessageNotReadableException) {
        this.recordSystemException(request, httpMessageNotReadableException);

        log.error("MissingServletRequestParameterException (请求提交信息无法读取异常):"+httpMessageNotReadableException.getMessage(),
                httpMessageNotReadableException);
        ResultBean resultBean=new ResultBean();
        resultBean.setError("请求提交信息格式错误，无法读取" );
        return resultBean;
    }

    private ResultBean interServerError() {
        ResultBean resultBean=new ResultBean();
        resultBean.setError("出现了点小意外,请稍后再试或联系管理员");
        return resultBean;
    }


    /**
      * 复制ta404框架中的异常记录方法。该方法发送异常日志（log_exception）事件，由框架中的异常处理类处理保存
      * @Author 柳时光
      * @Date 2019/5/29 15:55
      * @param request
      * @param exception
      * @return void
      **/
    private void recordSystemException(HttpServletRequest request, Exception exception) {
      /*  Map eventDto = new HashMap();
        eventDto.put("parameters", request.getParameterMap());
        eventDto.put("clientIp", WebUtil.getClientIp(request));
        eventDto.put("userAgent", WebUtil.getUserAgent(request));
        eventDto.put("url", WebUtil.getRequestUrl(request));
        eventDto.put("exception", exception);
        eventDto.put("host", host);
        eventDto.put("port", port);
        this.eventPublish.publish(eventDto, "log_exception");*/
    }

    static {
        //启动时，获取服务器IP，端口
        InetAddress localAddress = NetUtils.getLocalInetAddress();
        if (localAddress != null) {
            host = localAddress.getHostAddress();
        } else {
            host = "";
        }

        port = String.valueOf(NetUtils.getServerPort());
    }

}
